package org.mech.sefer.event.bus.impl;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Stack;

import org.mech.sefer.event.Event;
import org.mech.sefer.event.Handler;
import org.mech.sefer.event.bus.EventBus;
import org.mech.sefer.event.register.HandlerRegistration;
import org.mech.sefer.event.register.HandlerRegistrationInfo;
import org.mech.sefer.event.register.HandlerRegistrationInfo.Order;
import org.mech.sefer.event.register.HandlerRegistry;
import org.mech.sefer.event.register.impl.HandlerRegistrationImpl;
import org.mech.sefer.event.register.impl.HandlerRegistryImpl;

/**
 * <p>
 * The DefaultEventBus is default event bus implementation.
 * </p>
 * <p>
 * Date: 24.4.2012 8:37:58
 * </p>
 *
 * @author martin.krajc
 */
public class DefaultEventBus implements EventBus {

	private Map<Class<?>, HandlerRegistry> registryMap = new HashMap<Class<?>, HandlerRegistry>();
	private Stack<Event<Handler>> eventStack = new Stack<Event<Handler>>();
	private Queue<Event<Handler>> waitingEvents = new LinkedList<Event<Handler>>();

	/**
	 * {@inheritDoc}
	 */
	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void fireEvent(Event event) {
		if (registryMap.containsKey(event.getClass())) {
			Iterator<Handler> iterator = registryMap.get(event.getClass())
					.handlers();

			if (!eventStack.isEmpty() && eventStack.peek().isForced()) {
				waitingEvents.offer(event);
				return;
			}

			// push element into stack
			eventStack.push(event);

			while (iterator.hasNext()) {
				Handler handler = (Handler) iterator.next();
				event.dispatch(handler);

				if (event.isDead()) {
					break;
				}

			}

			eventStack.pop();

			if (!waitingEvents.isEmpty()) {
				fireEvent(waitingEvents.poll());
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public HandlerRegistration registerHandler(Class<?> eventType, Handler handler) {
		return registerHandler(eventType, handler, new HandlerRegistrationInfo(
				false, Order.LAST));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public HandlerRegistration registerHandler(Class<?> eventType, Handler handler,
			HandlerRegistrationInfo info) {
		HandlerRegistry handlerRegistry = registryMap.get(eventType);

		if (handlerRegistry == null) {
			handlerRegistry = new HandlerRegistryImpl();
			registryMap.put(eventType, handlerRegistry);
		}

		return new HandlerRegistrationImpl(handlerRegistry, handler, info);
	}
}
